/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.primitives.stdlib;

import fr.x9c.cadmium.kernel.Block;
import fr.x9c.cadmium.kernel.ByteCodeRunner;
import fr.x9c.cadmium.kernel.CodeRunner;
import fr.x9c.cadmium.kernel.Context;
import fr.x9c.cadmium.kernel.Fail;
import fr.x9c.cadmium.kernel.Fatal;
import fr.x9c.cadmium.kernel.Primitive;
import fr.x9c.cadmium.kernel.PrimitiveProvider;
import fr.x9c.cadmium.kernel.Value;
import fr.x9c.cadmium.util.Stack;

/**
 * Implements all primitives from 'meta.c'.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.2
 * @since 1.0
 */
@PrimitiveProvider
public final class Meta {

    /**
     * No instance of this class.
     */
    private Meta() {
    } // end empty constructor

    /**
     * Returns the global data of the program.
     * @param ctxt context
     * @param unit ignored
     * @return the global data of the program
     * @throws Fail.Exception if the code runner is native
     */
    @Primitive
    public static Value caml_get_global_data(final CodeRunner ctxt,
                                             final Value unit)
        throws Fail.Exception {
        if (ctxt.isNative()) {
            Fail.invalidArgument("Meta.get_global_data");
        } // end if
        return ctxt.getContext().getGlobalData();
    } // end method 'caml_get_global_data(CodeRunner, Value)'

    /**
     * Throws 'Not_found'.
     * @param ctxt context
     * @param unit ignored
     * @return <i>unit</i>
     * @throws Fail.Exception if the code runner is native
     * @throws Fail.Exception always
     */
    @Primitive
    public static Value caml_get_section_table(final CodeRunner ctxt,
                                               final Value unit)
        throws Fail.Exception {
        if (ctxt.isNative()) {
            Fail.invalidArgument("Meta.get_section_table");
        } // end if
        Fail.raiseNotFound();
        return Value.UNIT; // never reached
    } // end method 'caml_get_section_table(CodeRunner, Value)'

    /**
     * Converts an array of bytes into bytecode.
     * @param ctxt context
     * @param prog closure bytecode
     * @param len bytecode length
     * @return a closure referring to the passed bytecode
     * @throws Fail.Exception if the code runner is native
     */
    @Primitive
    public static Value caml_reify_bytecode(final CodeRunner ctxt,
                                            final Value prog,
                                            final Value len)
        throws Fail.Exception {
        if (ctxt.isNative()) {
            Fail.invalidArgument("Meta.reify_bytecode");
            return Value.UNIT; // never reached
        } else {
            final byte[] b = prog.asBlock().getBytes();
            assert ((len.asLong() % 4) == 0)
                || (len.asLong() > b.length) : "invalid bytecode";
            final int ln = len.asLong() / 4;
            final int[] code = new int[ln];

            // fixes endianness as Java is big endian
            for (int i = 0; i < ln; i++) {
                code[i] = ((b[4 * i + 3] << 24) & 0xFF000000)
                    | ((b[4 * i + 2] << 16) & 0x00FF0000)
                    | ((b[4 * i + 1] << 8) & 0x0000FF00)
                    | (b[4 * i] & 0x000000FF);
            } // end for

            // appends additional code to existing
            final int codeOfs = ctxt.getContext().appendCode(code);

            // creates closure
            final Block clos = Block.createClosure(1);
            clos.setCode(codeOfs);
            return Value.createFromBlock(clos);
        } // end if/else
    } // end method 'caml_reify_bytecode(CodeRunner, Value, Value)'

    /**
     * Resizes the global data, if requested (passed) size is greater than
     * the current one.
     * @param ctxt context
     * @param size requested size for global data
     * @return <i>unit</i>
     * @throws Fail.Exception if the code runner is native
     */
    @Primitive
    public static Value caml_realloc_global(final CodeRunner ctxt,
                                            final Value size)
        throws Fail.Exception {
        if (ctxt.isNative()) {
            Fail.invalidArgument("Meta.realloc_global");
        } // end if
        ctxt.getContext().resizeGlobalData(size.asLong());
        return Value.UNIT;
    } // end method 'caml_realloc_global(CodeRunner, Value)'

    /**
     * Returns the thread environment.
     * @param ctxt context
     * @param unit ignored
     * @return the thread environment
     */
    @Primitive
    public static Value caml_get_current_environment(final CodeRunner ctxt,
                                                     final Value unit) {
        return ((ByteCodeRunner) ctxt).getEnv();
    } // end method 'caml_get_current_environment(CodeRunner, Value)'

    /**
     * Constructs a stack configuration for traced function.
     * @param ctxt context
     * @param codePtr code address of traced function
     * @param env environment
     * @param arg argument to function
     * @return <i>unit</i>
     * @throws Fail.Exception if the code runner is native
     * @throws Fail.Exception if a stack overflow occurs
     * @throws Fatal.Exception if a stack underflow occurs
     */
    @Primitive
    public static Value caml_invoke_traced_function(final CodeRunner ctxt,
                                                    final Value codePtr,
                                                    final Value env,
                                                    final Value arg)
        throws Fail.Exception, Fatal.Exception {
        if (ctxt.isNative()) {
            Fail.invalidArgument("Meta.invoke_traced_function");
        } // end if
        final Stack stack = ((ByteCodeRunner) ctxt).getStack();
        final int valuesToMove = 6;
        final Value[] values = stack.popSlice(6);
        stack.push(arg);
        stack.push(Value.ZERO);
        stack.push(env);
        stack.push(codePtr);
        stack.pushSlice(values);
        return Value.UNIT;
    } // end method 'caml_invoke_traced_function(CodeRunner, Value, Value, Value)'

} // end class 'Meta'
